package com.youxin.third.auth.service.manager;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.youxin.third.auth.context.CacheKeyGenerator;
import com.youxin.third.auth.mapper.AccessTokenMapper;
import com.youxin.third.auth.mapper.ClientDetailsMapper;
import com.youxin.third.auth.mapper.OauthCodeMapper;
import com.youxin.third.auth.model.AccessToken;
import com.youxin.third.auth.model.ClientDetails;
import com.youxin.third.auth.model.OauthCode;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;

import static com.youxin.third.auth.context.CacheKeyGenerator.generateOauthCodeUsernameClientIdKey;

/**
 * description: OAuthManager <br>
 * date: 2020/3/7 16:23 <br>
 * author: llkj <br>
 * version: 1.0 <br>
 */
@Component
public class OAuthManager {

    private static final Logger logger = LoggerFactory.getLogger(OAuthManager.class);

    @Resource
    private OauthCodeMapper oauthCodeMapper;

    @Resource
    private AccessTokenMapper accessTokenMapper;

    @Resource
    private ClientDetailsMapper clientDetailsMapper;

    @Resource
    private RedisTemplate redisTemplate;

    /**
     * 向缓存中添加 缓存数据
     *
     * @param cache Cache
     * @param key   Cache key
     * @param value Cache value, null will return false
     * @return True is successful,otherwise false
     */
    protected boolean putToCache(Object key, Object value) {
        if (value == null) {
            logger.debug("Ignore put to cache key = {}, because value is null", key);
            return false;
        }
        redisTemplate.opsForValue().set(key, value);
        logger.debug("Put [{} = {}] to cache[", key, value);
        return true;
    }

    protected Object getFromCache(Object key) {
        Object object = redisTemplate.opsForValue().get(key);
        return object == null ? null : object;
    }

    protected boolean evictFromCache(Object key) {
        if (key == null) {
            logger.debug("Ignore evict from cache[], because key is null");
            return false;
        }
        redisTemplate.delete(key);
        logger.debug("Evict key[{}] from cache", key);
        return true;
    }


    public int saveOauthCode(OauthCode oauthCode) {
        final String key = CacheKeyGenerator.generateOauthCodeKey(oauthCode);
        final String key1 = generateOauthCodeUsernameClientIdKey(oauthCode);
        putToCache(key, oauthCode);
        putToCache(key1, oauthCode);

        return oauthCodeMapper.insert(oauthCode);
    }


    public OauthCode findOauthCodeByUsernameClientId(String username, String clientId) {
        final String key = generateOauthCodeUsernameClientIdKey(username, clientId);
        OauthCode oauthCode = (OauthCode) getFromCache(key);
        if (oauthCode == null) {
            oauthCode = oauthCodeMapper.findOauthCodeByUsernameClientId(username, clientId);
            putToCache(key, oauthCode);
            logger.debug("Load OauthCode[{}] from DB and cache it, username = {},clientId = {} result: {}", oauthCode, username, clientId);
        }
        return oauthCode;
    }


    public int deleteOauthCode(OauthCode oauthCode) {
        final String key = generateOauthCodeUsernameClientIdKey(oauthCode);
        final String key1 = CacheKeyGenerator.generateOauthCodeKey(oauthCode);
        evictFromCache(key);
        evictFromCache(key1);

        return oauthCodeMapper.deleteOauthCode(oauthCode);
    }


    public int saveAccessToken(AccessToken accessToken) {
        //add to cache
        final String key = CacheKeyGenerator.generateAccessTokenKey(accessToken);
        final String key1 = CacheKeyGenerator.generateAccessTokenUsernameClientIdAuthIdKey(accessToken);

        putToCache(key, accessToken);
        putToCache(key1, accessToken);
        logger.debug("Cache AccessToken[{}], key = {}, key1 = {}", accessToken, key, key1);

        //refresh cache
        if (StringUtils.isNotEmpty(accessToken.getRefreshToken())) {
            final String key2 = CacheKeyGenerator.generateAccessTokenRefreshKey(accessToken);
            putToCache(key2, accessToken);
            logger.debug("Cache AccessToken[{}] by refresh-token, key = {}", accessToken, key2);
        }

        return accessTokenMapper.insert(accessToken);
    }


    public AccessToken findAccessToken(String clientId, String username, String authenticationId) {
        final String key = CacheKeyGenerator.generateAccessTokenUsernameClientIdAuthIdKey(username, clientId, authenticationId);
        AccessToken accessToken = (AccessToken) getFromCache(key);

        if (accessToken == null) {
            accessToken = accessTokenMapper.findAccessToken(clientId, username, authenticationId);
            putToCache(key, accessToken);
            logger.debug("Load AccessToken[{}] from DB and cache it, clientId = {}, username = {}, authenticationId = {}", accessToken, clientId, username, authenticationId);
        }
        return accessToken;
    }


    public AccessToken findAccessTokenByTokenId(String tokenId) {
        final String key = CacheKeyGenerator.generateAccessTokenKey(tokenId);
        AccessToken accessToken = (AccessToken) getFromCache(key);

        if (accessToken == null) {
            accessToken = accessTokenMapper.selectOne(new LambdaQueryWrapper<AccessToken>().eq(AccessToken::getTokenId, tokenId));
            putToCache(key, accessToken);
            logger.debug("Load AccessToken[{}] from DB and cache it, key = {}", accessToken, key);
        }
        return accessToken;
    }


    public int deleteAccessToken(AccessToken accessToken) {
        final String key = CacheKeyGenerator.generateAccessTokenKey(accessToken);
        final String key1 = CacheKeyGenerator.generateAccessTokenUsernameClientIdAuthIdKey(accessToken);

        evictFromCache(key);
        evictFromCache(key1);
        logger.debug("Evict AccessToken[{}] from cache, key = {}, key1 = {}", accessToken, key, key1);
        return accessTokenMapper.deleteAccessToken(accessToken);
    }


    public OauthCode findOauthCode(String code, String clientId) {

        final String key = CacheKeyGenerator.generateOauthCodeKey(code, clientId);
        OauthCode oauthCode = (OauthCode) getFromCache(key);

        if (oauthCode == null) {
            oauthCode = oauthCodeMapper.findOauthCode(code, clientId);
            putToCache(key, oauthCode);
            logger.debug("Load OauthCode[{}] from DB and cache it, code = {}, clientId = {}", oauthCode, code, clientId);
        }

        return oauthCode;
    }


    public AccessToken findAccessTokenByRefreshToken(String refreshToken, String clientId) {

        final String key = CacheKeyGenerator.generateAccessTokenRefreshKey(refreshToken, clientId);
        AccessToken accessToken = (AccessToken) getFromCache(key);

        if (accessToken == null) {
            accessToken = accessTokenMapper.findAccessTokenByRefreshToken(refreshToken, clientId);
            putToCache(key, accessToken);
            logger.debug("Load AccessToken[{}] from DB and cache it, refreshToken = {}, clientId = {}", accessToken, refreshToken, clientId);
        }

        return accessToken;
    }


    public ClientDetails findClientDetails(String clientId) {

        final String key = CacheKeyGenerator.generateClientDetailsKey(clientId);
        ClientDetails clientDetails = (ClientDetails) getFromCache(key);

        if (clientDetails == null) {
            clientDetails = clientDetailsMapper.selectOne(new LambdaQueryWrapper<ClientDetails>().eq(ClientDetails::getClientId, clientId));
            putToCache(key, clientDetails);
            logger.debug("Load ClientDetails[{}] from DB and cache it, clientId = {}", clientDetails, clientId);
        }

        return clientDetails;
    }


    public int updateByClientDetails(ClientDetails clientDetails) {
        final String key = CacheKeyGenerator.generateClientDetailsKey(clientDetails.getClientId());
        evictFromCache(key);
        return clientDetailsMapper.updateById(clientDetails);
    }


    public void saveClientDetails(ClientDetails clientDetails) {
        clientDetailsMapper.insert(clientDetails);
    }


    public IPage<ClientDetails> pageClientDetails(Page<ClientDetails> page, LambdaQueryWrapper<ClientDetails> queryWrapper) {
        return clientDetailsMapper.selectPage(page,queryWrapper);
    }
}
